package com.zzyl.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.zzyl.constant.SuperConstant;
import com.zzyl.dto.ResourceDto;
import com.zzyl.entity.Resource;
import com.zzyl.enums.BasicEnum;
import com.zzyl.exception.BaseException;
import com.zzyl.mapper.ResourceMapper;
import com.zzyl.service.ResourceService;
import com.zzyl.utils.NoProcessing;
import com.zzyl.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
@Transactional
@Slf4j
public class ResourceServiceImpl implements ResourceService {

    @Autowired
    private ResourceMapper resourceMapper;
    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    /**
     * 根据用户id查询对应的资源数据
     */
    @Override
    @Cacheable(cacheNames = "RESOURCE::MENUS",key = "#userId",unless = "#result.size()==0")
    public List<MenuVo> menus(Long userId) {
        //查询数据
        List<MenuVo> menuVoList = resourceMapper.selectMenuByUserId(userId);
        //构建 parentNo -> List<MenuVo> 的映射
        Map<String, List<MenuVo>> parentMap = menuVoList.stream().collect(Collectors.groupingBy(MenuVo::getParentResourceNo));

        //递归构建我的菜单信息
        buildTree(menuVoList, parentMap);
        return parentMap.get(SuperConstant.ROOT_RESOURCE_PARENT_ID);
    }

    /**
     * 递归构建我的菜单信息（已提供）
     * 递归构建我的菜单信息（已提供）
     * 递归构建我的菜单信息（已提供）
     */
    private void buildTree(List<MenuVo> menuVoList, Map<String, List<MenuVo>> parentMap) {
        menuVoList.forEach(menuVo -> {
            //补全数据
            menuVo.setMeta(MenuMetaVo.builder().title(menuVo.getName()).build());
            menuVo.setRedirect("/" + menuVo.getName());
            //根据当前资源编号查询子资源
            List<MenuVo> childrenList = parentMap.get(menuVo.getResourceNo());
            if (CollUtil.isNotEmpty(childrenList)) {
                buildTree(childrenList, parentMap);
                menuVo.setChildren(childrenList);
            }
        });
    }

    /**
     * 资源列表
     */
    @Override
    @Cacheable(cacheNames = "RESOURCE::LIST",key = "#resourceDto.hashCode()",unless = "#result.size()==0")
    public List<ResourceVo> findList(ResourceDto resourceDto) {
        // 1、参数处理
        // 1、1 如果用户没有设置查询的数据状态，默认查询可用状态菜单
//        if(resourceDto.getDataState() == null){
//            resourceDto.setDataState(SuperConstant.DATA_STATE_0);
//        }
        // 1、2如果客户端传入了父菜单id,查询父菜单下的子菜单，需要处理父菜单id
        if(StrUtil.isNotEmpty(resourceDto.getResourceNo())){
            resourceDto.setParentResourceNo(NoProcessing.processString(resourceDto.getParentResourceNo()));
        }
        // 2、调用数据访问层查询菜单
        List<Resource> resources=resourceMapper.findList(resourceDto.getParentResourceNo(),resourceDto.getResourceType(),resourceDto.getDataState());
        return BeanUtil.copyToList(resources, ResourceVo.class);
    }

    /**
     * 资源树形
     */
    @Override
    @Cacheable(cacheNames = "RESOURCE::TREE",key = "#resourceDto.hashCode()",unless = "#result==null")
    public TreeVo findTree(ResourceDto resourceDto) {
        // 引入缓存，解决资源加载过慢，业务处理耗时的问题
//        String resourceTree = redisTemplate.opsForValue().get("RESOURCE::Tree");
//        if(StrUtil.isNotEmpty(resourceTree)){
//            log.info("命中redis缓冲，获取到资源树数据");
//            return JSONUtil.toBean(resourceTree,TreeVo.class);
//        }
//        log.info("没有命中redis缓冲，从数据库加载资源库数据");
        //1. 查询所有的资源菜单
        List<Resource> resourceList = resourceMapper.findChildList(NoProcessing.processString
                (SuperConstant.ROOT_DEPT_PARENT_ID),SuperConstant.MENU,SuperConstant.DATA_STATE_0);
        //如果没有资源 , 直接返回空集合
        if(CollectionUtil.isEmpty(resourceList)){
           return new TreeVo(Collections.emptyList());
        }
        //2. 存在资源解析树形结构
        //2.1 获取跟节点
        Optional<Resource> first = resourceList.stream().filter(resource -> StrUtil.equals(resource.getResourceNo()
                , SuperConstant.ROOT_RESOURCE_PARENT_ID)).findFirst();
        Resource root = null;
        if(first.isPresent()){
            root = first.get();
        }else {
            root = new Resource();
            root.setResourceNo(SuperConstant.ROOT_RESOURCE_PARENT_ID);
            root.setResourceName("智慧养老院");
        }
        ArrayList<TreeItemVo> treeItemVoList = new ArrayList<>();
        // 递归构建树型结构
        recursionResourceList(root,treeItemVoList,resourceList);
        // 存入到redis缓冲当中
//        log.info("第一次获取资源树，存入到redis缓冲当中");
//        TreeVo treeVo = new TreeVo(treeItemVoList);
//        redisTemplate.opsForValue().set("RESOURCE::Tree",JSONUtil.toJsonStr(treeVo));
        return TreeVo.builder().items(treeItemVoList).build();
    }

    /**
     * 查询指定资源的子资源
     * 递归
     * @param root
     * @param treeItemVoList
     * @param resourceList   原始数据集合
     */
    private void recursionResourceList(Resource root, ArrayList<TreeItemVo> treeItemVoList, List<Resource> resourceList) {
       // 将父节点构建为树节点
        TreeItemVo treeItemVo = TreeItemVo.builder().id(root.getResourceNo()).label(root.getResourceName()).build();
        // 获取该节点下的所有子节点
        List<Resource> children = resourceList.stream()
                .filter(resource -> StrUtil.equals(resource.getParentResourceNo(), treeItemVo.getId()))
                .sorted(Comparator.comparing(Resource::getSortNo))
                .collect(Collectors.toList());
        if(CollectionUtil.isNotEmpty(children)){
            ArrayList<TreeItemVo> listChildren = new ArrayList<>();
            children.stream().forEach(resource -> {
                recursionResourceList(resource,listChildren,resourceList);
            });
            treeItemVo.setChildren(listChildren);
        }
        treeItemVoList.add(treeItemVo);
    }

    /**
     * 添加资源
     */
    @Override
    @Caching(evict = {
            @CacheEvict(cacheNames = "RESOURCE::LIST",allEntries = true),
            @CacheEvict(cacheNames = "RESOURCE::TREE",allEntries = true)
    })
    public void addResource(ResourceDto resourceDto) {
        //修改、删除、新增都要删除缓存，保证redis缓存和数据库的数据一致性
        redisTemplate.delete("RESOURCE::Tree");
        //1. 数据类型转化
        Resource resource = BeanUtil.toBean(resourceDto, Resource.class);
        //2. 查询父菜单状态, 让子菜单状态和父保持一致
        Resource parent = resourceMapper.findByResourceNo(resource.getParentResourceNo());
        if (parent == null) {
            throw new BaseException(BasicEnum.PARAMETER_MISSING);
        }
        // 设置子菜单的状态和父一致resource_type
        resource.setDataState(parent.getDataState());
        // 判断菜单层级不能超过三级(按钮不限制层级)
        // 判断新增的菜单是不是按钮
        // 资源类型：s平台 c目录 m菜单 r按钮
        boolean isButton = StrUtil.equals(resourceDto.getResourceType(), SuperConstant.BUTTON);
        // 智慧养老院(2)     1级菜单(1)   2级菜单(1)    3级菜单(1)
        if (!isButton && NoProcessing.processString(parent.getResourceNo()).length() / 3 >= 5) {
            throw new BaseException(BasicEnum.RESOURCE_DEPTH_UPPER_LIMIT);
        }
        //3. 生成资源编号
        //3.1 查询子菜单列表
        List<Resource> resourceList = resourceMapper.findChildList2(parent.getResourceNo());
        //3.2 生成资源编号
        String resourceNo = null;
        if (CollectionUtil.isEmpty(resourceList)) {
            //3.2.1 如果当前父菜单没有子菜单 , 在父菜单编号基础上增加即可
            resourceNo = NoProcessing.createNo(parent.getResourceNo(), false);
        } else {
            //3.2.2 如果当前父菜单存在子菜单 , 找到编号最大的子菜单, 在其基础上增加
            Long MaxNo = resourceList.stream()
                    // 将流中的菜单对象转化为Long类型的菜单编号
                    .map(item -> Long.valueOf(item.getResourceNo()))
                    //取编号的最大值
                    .max(Comparator.comparing(id -> id))
                    .get();
            resourceNo = NoProcessing.createNo(String.valueOf(MaxNo), true);
        }
        //设置资源编号到实体类
        resource.setResourceNo(resourceNo);
        //4. 新增数据到数据库
        resourceMapper.insert(resource);
    }

    /**
     * 启用禁用
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Caching(evict = {
            @CacheEvict(cacheNames = "RESOURCE::LIST",allEntries = true),
            @CacheEvict(cacheNames = "RESOURCE::TREE",allEntries = true)
    })
    public void modifyResourceStatus( ResourceDto resourceDto) {
        //1. 数据类型转化
        Resource resource = BeanUtil.toBean(resourceDto, Resource.class);
        //2、如果为空就抛出异常提示
        if(resource == null){
            throw  new BaseException(BasicEnum.PARAMETER_MISSING);
        }
        if(resource.getDataState().equals("0")){
            //3、 判断父级菜单是否是禁用，如果是禁用，不允许启用
            Resource parent = resourceMapper.findByResourceNo(resource.getParentResourceNo());
            if(parent.getDataState().equals("1")){
                throw new BaseException(BasicEnum.PARENT_MENU_DISABLE);
            }
        }
        //4、判断是否有子菜单，如果有子菜单，则一起启用或禁用
        String resourceNo = resource.getResourceNo();
        // 根据资源编号修改资源状态
        resourceMapper.updateByResourceNo(resourceNo, resource.getDataState());
        // 根据父资源编号修改资源状态
        resourceMapper.updateByParentResourceNo(NoProcessing.processString(resourceNo), resource.getDataState());
    }

    /**
     * 删除菜单
     */
    @Override
    @Transactional
    @Caching(evict = {
            @CacheEvict(cacheNames = "RESOURCE::LIST",allEntries = true),
            @CacheEvict(cacheNames = "RESOURCE::TREE",allEntries = true)
    })
    public void deleteResource(String menuId) {
        //修改、删除、新增都要删除缓存，保证redis缓存和数据库的数据一致性
        redisTemplate.delete("RESOURCE::Tree");
        // 根据资源编号查询资源详情
        Resource resource = resourceMapper.findByResourceNo(menuId);
        // 启用状态下，不可删除
        if("0".equals(resource.getDataState())){
            throw new BaseException(BasicEnum.ENABLED_CANNOT_DELETED);
        }
        // 对资源编号进行去 0
        String ParentResourceNo = NoProcessing.processString(menuId);
        // 根据当前资源编号 , 查询当前资源的子资源列表
        List<Resource> childList = resourceMapper.findChildList3(ParentResourceNo);
        // 判断子资源列表是否为空，不为空抛出异常提示
        if(CollectionUtil.isNotEmpty(childList)){
            throw new BaseException(BasicEnum.PARENT_MENU_Child);
        }
        // 删除菜单
        resourceMapper.deleteResource(menuId);
    }

    /**
     * 编辑菜单
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @Caching(evict = {
            @CacheEvict(cacheNames = "RESOURCE::LIST",allEntries = true),
            @CacheEvict(cacheNames = "RESOURCE::TREE",allEntries = true),
            @CacheEvict(cacheNames = "RESOURCE::MENUS",allEntries = true)
    })
    public void updateResource(ResourceDto resourceDto) {
        //修改、删除、新增都要删除缓存，保证redis缓存和数据库的数据一致性
        redisTemplate.delete("RESOURCE::Tree");
        //1. 查询菜单详情
        Resource resource = resourceMapper.findByResourceNo(resourceDto.getResourceNo());
        if(null == resource){
            throw new BaseException(BasicEnum.PARAMETER_MISSING);
        }
        //2. 判断用户是否修改了父菜单
        //2.1 如果没有修改父菜单, 直接更新数据库就可以了
        if(StrUtil.equals(resourceDto.getParentResourceNo(),resource.getParentResourceNo())){
            resource.setResourceName(resourceDto.getResourceName());
            resource.setRequestPath(resourceDto.getRequestPath());
            resource.setSortNo(resourceDto.getSortNo());
            resourceMapper.modefyByResource(resource);
            return;
        }
        //2.2 如果用户修改了父菜单 , 需要调整层级和菜单编号
        //生成新的编号
        String newResourceNo = createResourceNo(resourceDto.getParentResourceNo());
        // 如果新的菜单层级大于3级 , 则直接返回错误信息
        if(newResourceNo.length() /3 >5){
            throw new BaseException(BasicEnum.RESOURCE_DEPTH_UPPER_LIMIT);
        }
        //调整子菜单层级和编号(将旧的父菜单编号替换成新的, 旧的菜单编号替换成新的)
        List<Resource> childList = resourceMapper.findList(NoProcessing.processString(resource.getResourceNo()),null,null);
        // 过滤掉自己（自己得菜单编号已经重新生成，只需要调整子就可用）
        childList = childList.stream().filter(child -> !StrUtil.equals(child.getResourceNo(), resource.getResourceNo())).collect(Collectors.toList());
        // 将旧的父菜单编号替换为新得父菜单编号
        childList.stream().forEach(child->{
            // 可以将资源编号看成是两部分 : 父菜单编号部分 + 子菜单编号部分(除去原来的父菜单编号部分剩下的就是自己的)
            // 为子菜单生成新的父菜单编号 : 新的父菜单编号 + 子菜单父编号部分
            String parentResourceNo = NoProcessing.processString(newResourceNo) + child.getParentResourceNo().replace(NoProcessing.processString(resource.getResourceNo()), "");
            // 为子菜单生成新的菜单编号 : 新的父菜单编号 + 子菜单编号部分
            String n_resourceNo = NoProcessing.processString(newResourceNo) + child.getResourceNo().replace(NoProcessing.processString(resource.getResourceNo()), "");
            // 处理完成后长度可能超过15,去除后面多余的0
            // 例如 : 现有菜单编号 : 100001001001000   这个菜单之前父菜单编号 100001001000000   调整到 100001001001001000
            // 则按照规则得到的新编号为 :   100001001001000
            parentResourceNo = NoProcessing.processString(parentResourceNo);
            n_resourceNo = NoProcessing.processString(n_resourceNo);
            //去除零之后长度不满15 , 后面补0
            if(parentResourceNo.length() < 15){
                parentResourceNo = StrUtil.fillAfter(parentResourceNo,'0',15);
            }
            if(n_resourceNo.length() < 15){
                n_resourceNo = StrUtil.fillAfter(n_resourceNo,'0',15);
            }
            // 如果处理完成后菜单的层级超过3级 , 返回错误信息
            if(StrUtil.equals(child.getResourceType(), SuperConstant.MENU)){
                if(n_resourceNo.length()/3 >5){
                    throw new BaseException(BasicEnum.RESOURCE_DEPTH_UPPER_LIMIT);
                }
            }else {
                n_resourceNo = parentResourceNo + StrUtil.subSuf(n_resourceNo, n_resourceNo.length() - 3);
            }
            // 为子菜单设置最新的资源id和父资源id
            child.setResourceNo(n_resourceNo);
            child.setParentResourceNo(parentResourceNo);
        });
        //4. 修改菜单数据和子菜单数据
        // 更新父菜单数据
        resource.setResourceName(resourceDto.getResourceName());
        resource.setRequestPath(resourceDto.getRequestPath());
        resource.setSortNo(resourceDto.getSortNo());
        resource.setParentResourceNo(resourceDto.getParentResourceNo());
        resource.setResourceNo(newResourceNo);
        resourceMapper.update(resource);
        // 跟新子菜单数据
        childList.stream().forEach(child -> resourceMapper.update(child));
    }

    private String createResourceNo(String parentResourceNo) {
        //3.1 查询子菜单列表
        List<Resource> childList = resourceMapper.findChildList3(parentResourceNo);
        childList = childList.stream().filter(item -> StrUtil.equals(item.getResourceType(), SuperConstant.MENU)).collect(Collectors.toList());
        //3.2 生成资源编号
        String resourceNo = null;
        if(CollectionUtil.isEmpty(childList)){
            //3.2.1 如果当前父菜单没有子菜单 , 在父菜单编号基础上增加即可
            resourceNo = NoProcessing.createNo(parentResourceNo, false);
        }else {
            //3.2.2 如果当前父菜单存在子菜单 , 找到编号最大的子菜单, 在其基础上增加
            Long no = childList.stream()
                    // 将流中的菜单对象转化为Long类型的菜单编号
                    .map(item -> Long.valueOf(item.getResourceNo()))
                    //取编号的最大值
                    .max(Comparator.comparing(id -> id))
                    .get();
            resourceNo = NoProcessing.createNo(String.valueOf(no), true);
        }
        return resourceNo;
    }
}
